/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.visitors;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.ImageObserver;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import edu.cmu.ri.createlab.jj.blocks.*;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.*;
import edu.cmu.ri.createlab.jj.projects.*;
import edu.cmu.ri.createlab.jj.shapes.ShapeDefs;
import static edu.cmu.ri.createlab.jj.shapes.ShapeDefs.*;

public class BVDrawer implements BlockVisitorInterface
{
   //TODO change filenames to match updated icons
   
   
   ///Images to use to build the blocks when drawing
   //private Image[] classIcons;
   private Image[] flowIcons;
   private Image[] methIcons;
   private Image[] commIcons;
   private Image[] blockIcons;
   private Image[] vspaceIcons;
   private Image[] opIcons;
   private Image[] varIcons;
   private Image compileError;
   ///The top left corner where this will start drawing
   private int x;
   private int y;
   ///The height of the attachment tab at the bottom of blocks
   private int tabHeight;
   private double scale;
   ///Top left and bottom right coords for a rectangular bound of the last thing drawn
   private int bounds[];
   ///The graphics object to use for drawing
   private Graphics g;
   ///The panel that we're drawing on
   ImageObserver o;
   ///The font to use for drawing strings
   private Font f;
   private Font tf;
   private Font fBase;
   ///L = left, M = middle, R = right, P = preview, t = top, b = bottom
   ///bool_o is an offset for boolean versions
   ///title_o is an offset for a method title vs a method block
   
   
   
   
   
   
   public BVDrawer()
   {
      System.out.println("Put me here! " + System.getProperty("user.home") + "/JubJub/");
      String iconDir;
      String fontDir;
      String jjDir;
      if(System.getProperty("os.name").toLowerCase().contains("win"))
      {
         jjDir = System.getProperty("user.dir");
         iconDir = jjDir + iconDirRel;
         fontDir = jjDir + fontDirRel;
      }
      else
      {
         jjDir = System.getProperty("user.home") + "/JubJub";
         iconDir = jjDir + iconDirRel;
         fontDir = jjDir + fontDirRel;
      }
      
//      classIcons = new Image[4];
//      classIcons[ICON_L] = Toolkit.getDefaultToolkit().getImage(iconDir + class_l);
//      classIcons[ICON_M] = Toolkit.getDefaultToolkit().getImage(iconDir + class_m);
//      classIcons[ICON_R] = Toolkit.getDefaultToolkit().getImage(iconDir + class_r);
//      classIcons[ICON_P] = Toolkit.getDefaultToolkit().getImage(iconDir + class_p);
      
      methIcons = new Image[METH_PIECES];
      methIcons[METH_T] = Toolkit.getDefaultToolkit().getImage(iconDir + meth_t);
      methIcons[METH_M] = Toolkit.getDefaultToolkit().getImage(iconDir + meth_m);
      methIcons[METH_B] = Toolkit.getDefaultToolkit().getImage(iconDir + meth_b);
      methIcons[METH_R] = Toolkit.getDefaultToolkit().getImage(iconDir + meth_r);
      
      commIcons = new Image[COMM_PIECES];
      commIcons[COMM_T] = Toolkit.getDefaultToolkit().getImage(iconDir + comm_t);
      commIcons[COMM_M] = Toolkit.getDefaultToolkit().getImage(iconDir + comm_m);
      commIcons[COMM_B] = Toolkit.getDefaultToolkit().getImage(iconDir + comm_b);
      
      blockIcons = new Image[BLOCK_PIECES];
      blockIcons[BLOCK_T] = Toolkit.getDefaultToolkit().getImage(iconDir + block_t);
      blockIcons[BLOCK_M] = Toolkit.getDefaultToolkit().getImage(iconDir + block_m);
      blockIcons[BLOCK_B] = Toolkit.getDefaultToolkit().getImage(iconDir + block_b);
      blockIcons[SYM_IN] = Toolkit.getDefaultToolkit().getImage(iconDir + sym_in);
      blockIcons[SYM_OUT] = Toolkit.getDefaultToolkit().getImage(iconDir + sym_out);
      
      vspaceIcons = new Image[VSPACE_PIECES];
      vspaceIcons[VSPACE_T] = Toolkit.getDefaultToolkit().getImage(iconDir + vspace_t);
      vspaceIcons[VSPACE_M] = Toolkit.getDefaultToolkit().getImage(iconDir + vspace_m);
      vspaceIcons[VSPACE_B] = Toolkit.getDefaultToolkit().getImage(iconDir + vspace_b);
      
      varIcons = new Image[VAR_PIECES];
      varIcons[VAR_NUM_BOX] = Toolkit.getDefaultToolkit().getImage(iconDir + var_num_box);
      varIcons[VAR_BOOL_BOX] = Toolkit.getDefaultToolkit().getImage(iconDir + var_bool_box);
      varIcons[VAR_FLOAT_SYM] = Toolkit.getDefaultToolkit().getImage(iconDir + var_float_sym);
      varIcons[VAR_INT_SYM] = Toolkit.getDefaultToolkit().getImage(iconDir + var_int_sym);
      varIcons[VAR_BOOL_SYM] = Toolkit.getDefaultToolkit().getImage(iconDir + var_bool_sym);
      varIcons[VAR_OBJ_BOX] = Toolkit.getDefaultToolkit().getImage(iconDir + var_obj_box);
      varIcons[VAR_OBJ_SYM] = Toolkit.getDefaultToolkit().getImage(iconDir + var_obj_sym);

      opIcons = new Image[OP_PIECES];
      opIcons[OP_NUM_BOX] = Toolkit.getDefaultToolkit().getImage(iconDir + op_num_box);
      opIcons[OP_BOOL_BOX] = Toolkit.getDefaultToolkit().getImage(iconDir + op_bool_box);
      opIcons[OP_ASS_BOX] = Toolkit.getDefaultToolkit().getImage(iconDir + op_ass_box);
      opIcons[OP_BOOL_SYM] = Toolkit.getDefaultToolkit().getImage(iconDir + op_bool_sym);
      
      flowIcons = new Image[FLOW_PIECES];
      flowIcons[FLOW_IF] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_if);
      flowIcons[FLOW_WHILE] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_while);
      flowIcons[FLOW_ELSE] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_else);
      flowIcons[FLOW_END] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_end);
      flowIcons[FLOW_END_F] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_end_f);
      flowIcons[FLOW_MID_F] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_mid_f);
      flowIcons[FLOW_MID_T] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_mid_t);
      flowIcons[FLOW_MID] = Toolkit.getDefaultToolkit().getImage(iconDir +flow_mid);
      
      compileError = Toolkit.getDefaultToolkit().getImage(jjDir + error_icon);
      if(compileError == null) System.out.println(jjDir+error_icon);
      
      x = 0;
      y = 0;
      tabHeight = 10;
      scale = 1;
      bounds = new int[4];
      for(int i = 0; i < bounds.length; i++)
         bounds[i] = 0;
      this.g = null;
      this.o = null;
      String fontPath = fontDir+fontName;
      try
      {
         FileInputStream is = new FileInputStream(fontPath);
         fBase = Font.createFont(Font.TRUETYPE_FONT, is);
         f = fBase.deriveFont(Font.PLAIN, fontSize);
         tf = fBase.deriveFont(Font.PLAIN, Math.max(1, fontSize-fontDiff));
         System.out.println(fBase.getFontName());
      } catch (Exception e)
      {
         System.err.println(e.getMessage());
      }

   }
   
   public Image getErrIcon()
   {
      return compileError;
   }
   
   public void setStart(int x, int y)
   {
      this.x = x;
      this.y = y;
   }
   
   public void setScale(double scale)
   {
      this.scale = scale;
      try
      {
         this.f = fBase.deriveFont(Font.PLAIN, (int)(fontSize*scale));
         this.tf = fBase.deriveFont(Font.PLAIN, Math.max(1, (int)(fontSize*scale)-fontDiff));
      }
      catch(Exception e)
      {
         System.err.println(e.getMessage());
      }
   }
   
   public int[] getBounds()
   {
      return bounds;
   }
   
   public void setGraphics(Graphics g)
   {
      this.g = g;
   }
   
   public void setObserver(ImageObserver o)
   {
      this.o = o;
   }
   
   public void clearObserver()
   {
      this.o = null;
   }
   
   public void clearGraphics()
   {
      this.g = null;
   }
   
   public void drawAllIcons(Graphics g, ImageObserver obs)
   {
      //g.drawImage(classIcons[ICON_P], this.x, this.y, obs);
      g.setFont(f);
      int tempX = x;
//      for(int i = 0; i < classIcons.length; i++)
//      {
//         int height = (int) (classIcons[i].getHeight(obs)*scale);
//         int width = (int)(classIcons[i].getWidth(obs)*scale);
//         g.drawImage(classIcons[i],x,y, width, height, obs);
//         g.drawString("c" + i, x, y);
//         x += width;
//      }
//      y += classIcons[ICON_P].getHeight(obs);
//      x = tempX;

      int tempY = y;
      for(int i = 0; i < methIcons.length; i++)
      {
         int height = (int) (methIcons[i].getHeight(obs)*scale);
         int width = (int)(methIcons[i].getWidth(obs)*scale);
         g.drawImage(methIcons[i],x,y, width, height, obs);
         y+= height;         
         g.drawString("m" + i, x, y);
      }
      
      for(int i = 0; i < blockIcons.length; i++)
      {
         int height = (int) (blockIcons[i].getHeight(obs)*scale);
         int width = (int)(blockIcons[i].getWidth(obs)*scale);
         g.drawImage(blockIcons[i],x,y, width, height, obs);
         y+= height;         
         g.drawString("b" + i, x, y);
      }
      
      for(int i = 0; i < flowIcons.length; i++)
      {
         int height = (int) (flowIcons[i].getHeight(obs)*scale);
         int width = (int)(flowIcons[i].getWidth(obs)*scale);
         g.drawImage(flowIcons[i],x,y, width, height, obs);
         y+= height;         
         g.drawString("f" + i, x, y);
      }
      
      for(int i = 0; i < opIcons.length; i++)
      {
         int height = (int) (opIcons[i].getHeight(obs)*scale);
         int width = (int)(opIcons[i].getWidth(obs)*scale);
         g.drawImage(opIcons[i],x,y, width, height, obs);
         y+= height;
         g.drawString("o" + i, x, y);
      }
      
      for(int i = 0; i < varIcons.length; i++)
      {
         int height = (int) (varIcons[i].getHeight(obs)*scale);
         int width = (int)(varIcons[i].getWidth(obs)*scale);
         g.drawImage(varIcons[i],x,y, width, height, obs);
         y+= height;
         g.drawString("v" + i, x, y);
      }
      
   }

   
   public BVReturn finish()
   {
      // TODO Auto-generated method stub
      return null;
   }

   
   public BVTypes getBVType()
   {
      return BVTypes.DRAW;
   }

   
   public BVReturn visit(BlockBlock block)
   {
      if(g == null || o == null) return null;
      g.setFont(f);
      g.setColor(Color.white);
      ///Draw the top
      int height = (int) (mTitleTopHeight*scale);
      int width = (int) (mTitleWidth*scale);
      g.drawImage(methIcons[METH_T], x, y, width, height, o);
      g.drawString(block.getName(),(int)(x+nameXOffset*scale), (int)(y+nameYOffset*scale));
      ///Draw the middles
      int tempY = y + height;
      height = (int)(middleHeight*scale);
      width = (int)(mTitleWidth*scale);
      g.setFont(tf);
      for(int i = 0; i < block.getParams().size(); i++)
      {
         g.drawImage(methIcons[METH_M], x, tempY, width, height, o);
         if(block.getParams().get(i).getVarType() != VarTypes.OBJECT)
            g.drawString(block.getParams().get(i).getVarType().toString(), 
                  x+(int)(varTypeXOffset*scale), tempY+(int)(varTypeYOffset*scale));
         else
            g.drawString(block.getParams().get(i).getObjectType(), 
                  x+(int)(varTypeXOffset*scale), tempY+(int)(varTypeYOffset*scale));
         
         tempY += height;
      }
      g.setFont(f);
      g.setColor(Color.black);
      g.drawImage(methIcons[METH_M], x, tempY, width, height, o);
      tempY += height;
      ///Draw the bottom
      height = (int)(mTitleBotHeight*scale);
      width = (int)(mTitleWidth*scale);
      g.drawImage(methIcons[METH_B], x, tempY, width, height, o);
      return null;
   }
   
   public BVReturn drawVarSpace(BlockBlock block)
   {
      if(g == null || o == null) return null;
      g.setFont(f);
      ///Draw the top
      int height = (int) (varSpaceTopHeight*scale);
      int width = (int) (varSpaceWidth*scale);
      g.drawImage(vspaceIcons[VSPACE_T], x, y, width, height, o);
      g.drawString("Variables",(int)(x+nameXOffset*scale), (int)(y+nameYOffset*scale));
      ///Draw the middles
      int tempY = y + height;
      int vNameY = tempY;
      height = (int)(middleHeight*scale);
      width = (int)(varSpaceWidth*scale);
      g.setFont(tf);
      for(int i = 0; i < block.getVars().size(); i++)
      {
         g.drawImage(vspaceIcons[VSPACE_M], x, tempY, width, height, o);
         vNameY = (int) (tempY + varTypeYOffset*scale);
         if(block.getVars().get(i).getVarType() != VarTypes.OBJECT)
            g.drawString(block.getVars().get(i).getVarType().toString(),
               (int) (x+varTypeXOffset*scale), vNameY);
         else
            g.drawString(block.getVars().get(i).getObjectType(),
                  (int) (x+varTypeXOffset*scale), vNameY);
         tempY += height;
      }
      g.drawImage(vspaceIcons[VSPACE_M], x, tempY, width, height, o);
      tempY += height;
      g.setFont(f);
      ///Draw the bottom
      height = (int)(varSpaceBotHeight*scale);
      width = (int)(varSpaceWidth*scale);
      g.drawImage(vspaceIcons[VSPACE_B], x, tempY, width, height, o);
      return null;
   }
   
   public BVReturn drawEnd(BlockBlock block)
   {
      if(g == null || o == null) return null;
      g.setFont(f);
      ///Draw the top
      int height = (int) (mReturnHeight*scale);
      int width = (int) (mTitleWidth*scale);
      g.drawImage(methIcons[METH_R], x, y, width, height, o);
      g.setColor(Color.white);
      g.drawString("return",(int)(x+nameXOffset*scale), (int)(y+nameYOffset*scale));
      g.setColor(Color.black);
      return null;
   }

   
   public BVReturn visit(CodeBlock block)
   {
      if(g == null || o == null) return null;
      ///Draw the top
      g.setFont(f);
      int height = (int) (topHeight*scale);
      int width = (int) (normalWidth*scale);
      g.drawImage(blockIcons[BLOCK_T], x, y, width, height, o);
      g.drawString(block.getName(),(int)(x+nameXOffset*scale), (int)(y+nameYOffset*scale));
      ///Draw the middles
      int tempY = y + height;
      height = (int)(middleHeight*scale);
      width = (int)(normalWidth*scale);
      for(int i = 0; i < block.getParams().size(); i++)
      {
         g.drawImage(blockIcons[BLOCK_M], x, tempY, width, height, o);
         //TODO if necessary add code to visit params/returns
         tempY += height;
      }
      ///Draw the bottom
      height = (int)(botHeight*scale);
      width = (int)(normalWidth*scale);
      g.drawImage(blockIcons[BLOCK_B], x, tempY, width, height, o);
      return null;
   }

   
   public BVReturn visit(ExpressionBlock block)
   {
      if(g == null || o == null) return null;
      g.setFont(f);
      if(block.getEType() == ExpressTypes.ARITH)
      {
         int width = (int) (opWidth*scale);
         int height = (int) (opHeight*scale);
         g.drawImage(opIcons[OP_NUM_BOX], x, y, width, height, o);
         g.drawString(block.getSymbol(), (int)(x+opSymXOffset*scale), (int)(y+opSymYOffset*scale));
      }
      else if(block.getEType() == ExpressTypes.LOGIC)
      {
         int width = (int) (opWidth*scale);
         int height = (int) (opHeight*scale);
         g.drawImage(opIcons[OP_BOOL_BOX], x, y, width, height, o);
         width = (int) (opSymWidth*scale);
         height = (int) (opSymHeight*scale);
         g.drawImage(opIcons[OP_BOOL_SYM], x, y, width, height, o);
         g.drawString(block.getSymbol(), (int)(x+opSymXOffset*scale), (int)(y+opSymYOffset*scale));
      }
      else if(block.getEType() == ExpressTypes.ASSIGN)
      {
         int width = (int) (assignWidth*scale);
         int height = (int) (assignHeight*scale);
         g.drawImage(opIcons[OP_ASS_BOX], x, y, width, height, o);
      }
      return null;
   }

   
   public BVReturn visit(FlowBlock block)
   {
      // TODO Auto-generated method stub
      return null;
   }

   
   public BVReturn visit(VarBlock block)
   {
      if(g == null || o == null) return null;
      //System.out.println("Drawing block " + block.getName() + " at " + x + " " + y);
      g.setFont(f);
      Image img;
      Image icon = null;
      if(block.getVarType() == VarTypes.BOOLEAN)
      {
         img = varIcons[VAR_BOOL_BOX];
         icon = varIcons[VAR_BOOL_SYM];
      }
      else if(block.getVarType() == VarTypes.OBJECT)
      {
         img = varIcons[VAR_OBJ_BOX];
         icon = varIcons[VAR_OBJ_SYM];
      }
      else
      {
         img = varIcons[VAR_NUM_BOX];
         if(block.getVarType() == VarTypes.INT)
            icon = varIcons[VAR_INT_SYM];
         else if(block.getVarType() == VarTypes.FLOAT)
            icon = varIcons[VAR_FLOAT_SYM];
      }
      int width = (int)(varWidth*scale);
      int height = (int)(varHeight*scale);
      
      g.drawImage(img, x, y, width, height, o);
      width = (int)(varSymWidth*scale);
      height = (int)(varHeight*scale);
      g.drawImage(icon, x, y, width, height, o);
      if(block.isLiteral())
         g.drawString(block.getValue(0), (int)(x+varTypeXOffset*scale), (int)(y+varTypeYOffset*scale));
      else
         g.drawString(block.getName(), (int)(x+varTypeXOffset*scale), (int)(y+varTypeYOffset*scale));
      return null;
   }

   
   public BVReturn visit(RefBlock block)
   {
      if(block == null) return null;
      if(block.getBlock() == null) 
      {
         drawVar(block);
         return null;
      }
      switch(block.getBlock().getType())
      {
      case BLOCK:
      case CODE:
         drawBlock(block);
         break;
      case FLOW:
         //TODO do something with flow refs
         drawFlowBlock(block);
         break;
      case OBJECT:
         drawObjectBlock(block);
         break;
      default:
         block.getBlock().accept(this);
         break;
      }
      
      return null;
   }

   
   public BVReturn visit(ObjectBlock block)
   {
      // TODO Auto-generated method stub
      return null;
   }

   
   public BVReturn visit(CommentBlock block)
   {
      // TODO Auto-generated method stub
      int width = (int)(commentWidth*scale);
      int height = (int)(topHeight*scale);
      int tempY = y;
      g.drawImage(commIcons[COMM_T], x, y, width, height, o);
      g.drawString("COMMENT",(int)(x+nameXOffset*scale),(int)(y+nameYOffset*scale));
      tempY += height;
      height = (int)(middleHeight*scale);
      for(int i = 0; i < commentSize; i++)
      {
         g.drawImage(commIcons[COMM_M],x,tempY,width,height,o);
         tempY += height;
      }
      height = (int)(botHeight*scale);
      g.drawImage(commIcons[COMM_B], x, tempY, width, height, o);
      return null;
   }

   
   public BVReturn visit(JJClass jjclass)
   {
      // TODO Auto-generated method stub
      return null;
   }

   
   public BVReturn visit(JJLibrary jjlib)
   {
      // TODO Auto-generated method stub
      return null;
   }

   
   public BVReturn visit(JJProject jjproj)
   {
      // TODO Auto-generated method stub
      return null;
   }
   
   public void drawFlowBlock(RefBlock block)
   {
      if(g == null || o == null) return;
      g.setFont(f);
      if(block.getBlock() == null)
      {
         //TODO error
         return;
      }
      int height = 0;
      int width = 0;
      if(((FlowBlock)block.getBlock()).getFType() == FlowTypes.WHILE)
      {
         if(block.getIndex() == 0)
         {
            width = (int)(flowWidth*scale);
            height = (int)(flowTopHeight*scale);
            g.drawImage(flowIcons[FLOW_WHILE], x, y, width, height, o);
            g.drawString(block.getName(), (int)(x + nameXOffset*scale), (int)(y + nameYOffset*scale));
         }
         if(block.getIndex() == 1 || block.getIndex() == 2)
         {
            //TODO While loops shouldn't have middles
         }
         if(block.getIndex() == 3)
         {
            width = (int)(flowWidth*scale);
            height = (int)(flowBotHeight*scale);
            g.drawImage(flowIcons[FLOW_END], x, y, width, height, o);
         }
      }
      else if(((FlowBlock)block.getBlock()).getFType() == FlowTypes.IFELSE)
      {
         if(block.getIndex() == 0)
         {
            width = (int)(flowWidth*scale);
            height = (int)(flowTopHeight*scale);
            g.drawImage(flowIcons[FLOW_WHILE], x, y, width, height, o);
            g.drawString(block.getName(), (int)(x + nameXOffset*scale), (int)(y + nameYOffset*scale));
         }
         if(block.getIndex() == 1 || block.getIndex() == 2)
         {
            width = (int)(flowWidth*scale);
            height = (int)(flowMidHeight*scale);
            g.drawImage(flowIcons[FLOW_ELSE], x, y, width, height, o);
            g.drawString(block.getName(), (int)(x + nameXOffset*scale), (int)(y + nameYOffset*scale));
         }
         if(block.getIndex() == 3)
         {
            width = (int)(flowWidth*scale);
            height = (int)(flowBotHeight*scale);
            g.drawImage(flowIcons[FLOW_END], x, y, width, height, o);
         }
      }
      else if(((FlowBlock)block.getBlock()).getFType() == FlowTypes.IF)
      {
         //TODO
      }
   }
   
   public void drawObjectBlock(RefBlock block)
   {
      if(g == null || o == null) return;
      g.setFont(f);
      if(block.getParams().isEmpty())
      {
         //TODO this is some sort of error
         return;
      }
      boolean isConstructor = false;
      if(block.getParams().get(0).getBlock().getType() == BlockTypes.BLOCK)
      {
         BlockBlock tempBlock = (BlockBlock)block.getParams().get(0).getBlock();
         isConstructor = tempBlock.isConstructor();
      }
      if(block.getParams().get(0).getBlock().getType() == BlockTypes.CODE)
      {
         CodeBlock tempBlock = (CodeBlock)block.getParams().get(0).getBlock();
         isConstructor = tempBlock.isConstructor();
      }
      String tempName = block.getParams().get(0).getName();
      //TODO add special drawing code for constructors
      if(!isConstructor)
      {
         block.getParams().get(0).setName(block.getName() + "." + tempName);
         block.getParams().get(0).accept(this);
         block.getParams().get(0).setName(tempName);
      }
      else 
      {
         block.getParams().get(0).accept(this);
         g.drawImage(varIcons[VAR_OBJ_BOX], (int)(x+nameXOffset*scale), (int)(y+varSpacing*scale),
               (int)(varWidth*scale), (int)(varHeight*scale), o);
         g.drawImage(varIcons[VAR_OBJ_SYM], (int)(x+nameXOffset*scale), (int)(y+varSpacing*scale),
               (int)(varSymWidth*scale), (int)(varHeight*scale), o);
         g.drawString(block.getName(),(int)(x+(nameXOffset+varNameXOffset)*scale), 
               (int)(y+nameYOffset*scale));
         //block.getParams().get(0).setName(block.getName() + " = new " + tempName);
      }
      //block.getParams().get(0).setName(tempName);
   }
   
   public void drawVar(RefBlock block)
   {
      if(g == null || o == null) return;
      int height = (int)(varHeight*scale);
      int width = (int)(varWidth*scale);
      g.drawImage(varIcons[VAR_NUM_BOX], x, y, width, height, o);
   }
   
   public void drawBlock(RefBlock block)
   {
      if(g == null || o == null) return;
      g.setFont(f);
      int size = 0;
      int tempY = y;
      int height = (int)(topHeight*scale);
      int width = (int)(normalWidth*scale);
      if(block.getBlock() == null)
      {
         g.drawImage(blockIcons[BLOCK_T], x, y, width, height, o);
         g.setColor(Color.white);
         g.drawString(block.getName(),(int)(x+nameXOffset*scale),(int)(y+nameYOffset*scale));
         tempY += height;
         width = (int)(normalWidth*scale);
         height = (int)(botHeight*scale);
         g.drawImage(blockIcons[BLOCK_B], x, tempY, width, height, o);
         return;
      }
      if(block.getBlock().getType() == BlockTypes.BLOCK)
      {
         //System.out.println("Drawing block " + block.getName());
         BlockBlock tempBlock = (BlockBlock)block.getBlock();
         size = tempBlock.getParams().size()+tempBlock.getReturns().size();
         
         g.drawImage(blockIcons[BLOCK_T], x, y, width, height, o);
         //TODO
         if(tempBlock.isConstructor())
            g.drawString("is a new " + block.getName(), (int)(x+constNameXOffset*scale), (int)(y+nameYOffset*scale));
         else
            g.drawString(block.getName(),(int)(x+nameXOffset*scale), (int)(y+nameYOffset*scale));
         //g.drawString(block.getName(),(int)(x+nameXOffset*scale),(int)(y+nameYOffset*scale));
         tempY += height;
         width = (int)(normalWidth*scale);
         height = (int)(middleHeight*scale);
         g.setFont(tf);
         for(int i = 0; i < size; i++)
         {
            g.drawImage(blockIcons[BLOCK_M], x, tempY, width, height, o);
            if(i < tempBlock.getParams().size()) 
            {
               String name = tempBlock.getParams().get(i).getVarType().toString();
               if(name.equals("object"))
                  name = tempBlock.getParams().get(i).getObjectType();
               name += "               ";
               name += tempBlock.getParams().get(i).getName();
               g.drawImage(blockIcons[SYM_IN], (int)(x+ioNameXOffset*scale), (int)(tempY+varSpaceYOffset*scale), (int)(ioSymWidth*scale), (int)(ioSymHeight*scale), o);
               g.drawString(" " + name, (int)(x+ioNameXOffset*scale + ioSymWidth*scale), (int)(tempY+ioNameYOffset*scale));
            }
            else
            {
               VarBlock tempVar = (VarBlock)tempBlock.getReturns().get(i-tempBlock.getParams().size()).getBlock();
               String name = tempVar.getVarType().toString();
               if(tempVar.getVarType() == VarTypes.OBJECT) 
                  name = tempVar.getObjectType();
               name += "               ";
               name += tempBlock.getReturns().get(i-tempBlock.getParams().size()).getName();
               g.drawImage(blockIcons[SYM_OUT], (int)(x+ioNameXOffset*scale), (int)(tempY+varSpaceYOffset*scale), (int)(ioSymWidth*scale), (int)(ioSymHeight*scale), o);
               g.drawString(" " + name, (int)(x+ioNameXOffset*scale + ioSymWidth*scale), (int)(tempY+ioNameYOffset*scale));
            }
            tempY += height;
         }
         g.setFont(f);
         width = (int)(normalWidth*scale);
         height = (int)(botHeight*scale);
         g.drawImage(blockIcons[BLOCK_B], x, tempY, width, height, o);
      }
      if(block.getBlock().getType() == BlockTypes.CODE)
      {
         //System.out.println("Drawing block " + block.getName());
         CodeBlock tempBlock = (CodeBlock)block.getBlock();
         size = tempBlock.getParams().size()+tempBlock.getReturns().size();
         
         g.drawImage(blockIcons[BLOCK_T], x, y, width, height, o);
         if(tempBlock.isConstructor())
            g.drawString("is a new " + block.getName(), (int)(x+constNameXOffset*scale), (int)(y+nameYOffset*scale));
         else
            g.drawString(block.getName(),(int)(x+nameXOffset*scale), (int)(y+nameYOffset*scale));
         //g.drawString(block.getName(),(int)(x+nameXOffset*scale),(int)(y+nameYOffset*scale));
         tempY += height;
         width = (int)(normalWidth*scale);
         height = (int)(middleHeight*scale);
         g.setFont(tf);
         for(int i = 0; i < size; i++)
         {
            //System.out.println(i + " at " + tempY);
            g.drawImage(blockIcons[BLOCK_M], x, tempY, width, height, o);
            if(i < tempBlock.getParams().size()) 
            {
               String name = tempBlock.getParams().get(i).getVarType().toString();
               if(name.equals("object"))
                  name = tempBlock.getParams().get(i).getObjectType();
               name += "                                              ";
               name += tempBlock.getParams().get(i).getName();
               g.drawImage(blockIcons[SYM_IN], (int)(x+ioNameXOffset*scale), (int)(tempY+varSpaceYOffset*scale), (int)(ioSymWidth*scale), (int)(ioSymHeight*scale), o);
               g.drawString(" " + name, (int)(x+ioNameXOffset*scale+ioSymWidth*scale), (int)(tempY+ioNameYOffset*scale));
            }
            else
            {
               VarBlock tempVar = tempBlock.getReturns().get(i-tempBlock.getParams().size());
               String name = tempVar.getVarType().toString();
               if(tempVar.getVarType() == VarTypes.OBJECT) 
                  name = tempVar.getObjectType();
               name += "                                             ";
               name += tempBlock.getReturns().get(i-tempBlock.getParams().size()).getName();
               g.drawImage(blockIcons[SYM_OUT], (int)(x+ioNameXOffset*scale), (int)(tempY+varSpaceYOffset*scale), (int)(ioSymWidth*scale), (int)(ioSymHeight*scale), o);
               g.drawString(" " + name, (int)(x+ioNameXOffset*scale+ioSymWidth*scale), (int)(tempY+ioNameYOffset*scale));
            }
            tempY += height;
         }
         g.setFont(f);
         width = (int)(normalWidth*scale);
         height = (int)(botHeight*scale);
         g.drawImage(blockIcons[BLOCK_B], x, tempY, width, height, o);
      }
      
   }
   
   public void drawIndent(int type, int height)
   {
      int width = (int)(indentWidth*scale);
      //int height = (int)(middleHeight*scale);
      g.drawImage(flowIcons[type], x, y, width, height, o);
   }
   
   public Font getFont()
   {
      return f;
   }
   
   public void drawAnImage(Image im, Graphics gr, ImageObserver obs)
   {
      gr.drawImage(im, 200, 200, obs);
   }
   
   public static void main(String[] args)
   {
      //BVDrawer drawer = new BVDrawer();
      JFrame frame = new JFrame("BVDrawer test");
      JScrollPane scroll = new JScrollPane();
      TestDrawingPanel panel = new TestDrawingPanel();
      Dimension psize = new Dimension(1024,1024);
      panel.setPreferredSize(psize);
      
      frame.setFont(new Font(fontName, Font.BOLD, fontSize));
      
      System.out.println(frame.getFont().getFontName());
      
      scroll.setViewportView(panel);
      scroll.setPreferredSize(new Dimension(800,600));
      
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(scroll);
      
      frame.pack();
      
      frame.setVisible(true);
      
   }
}

class TestDrawingPanel extends JPanel
{
   BVDrawer drawer;
   Image testImg;
   JJClass helloWorld; 
   
   TestDrawingPanel()
   {
      this.drawer = new BVDrawer();
      testImg = Toolkit.getDefaultToolkit().getImage("D:/Work/Masters Thesis/gsvn/src/JubJub/Graphics/blocks/meth_title_top.png");
      CodeBlock hwConst, sayHello;
      BlockBlock main;
      ObjectBlock name, hello, name2, hwArgs;
      VarBlock i, litName, litName2, exp1, exp2, one;
      RefBlock sayHelloRef, makeHW, helloRef, whileRef, whileRef2;
      ExpressionBlock setName, combineName, cLT5, ipp;
      CommentBlock cb;
      FlowBlock while1;
      
      AccessTypes[] pubAccess = {AccessTypes.PUBLIC};
      AccessTypes[] pubStatAcc = {AccessTypes.PUBLIC, AccessTypes.STATIC};
      
      LinkedList<TaggedCodePiece> sayHelloCode = new LinkedList<TaggedCodePiece>();
      sayHelloCode.add(new TaggedCodePiece("System.out.println(\"Hello \" + "));
      sayHelloCode.add(new TaggedCodePiece("name", 0));
      sayHelloCode.add(new TaggedCodePiece(" + \"!\");\n      return true;"));
      
      LinkedList<TaggedCodePiece> whileStart = new LinkedList<TaggedCodePiece>();
      whileStart.add(new TaggedCodePiece("while("));
      whileStart.add(new TaggedCodePiece("exp", 0));
      whileStart.add(new TaggedCodePiece(")"));
      //LinkedList<TaggedCodePiece> whileEnd = new LinkedList<TaggedCodePiece>();
      
      try
      {
         helloWorld = new JJClass("Hello World");
         hwConst = new CodeBlock("Hello World", helloWorld, 1);
         hwConst.setAccess(pubAccess);
         hwConst.setConstructor(true);
         helloWorld.addConstructor(hwConst);
         
         helloWorld.setComment("This is a class comment");
         
         ///Name of the VarBlock must be the same as the name in the tagged code
         name = new ObjectBlock("name", null, 2, 3, null);
         name.setObjectType("String");
         
         sayHello = new CodeBlock("say Hello", helloWorld, 4);
         sayHello.setTaggedCode(sayHelloCode);
         sayHello.addParam(name.getVariable());
         sayHello.setComment("This is a method comment");
         
         VarBlock boolRet = new VarBlock("success", null, 19, VarTypes.BOOLEAN, true);
         boolRet.setValue("true", 0);
         
         sayHello.addReturn(boolRet);
         
         helloWorld.addMethodBlock(sayHello);
         
         hello = new ObjectBlock("hello",null, 5, 6, helloWorld);
         helloWorld.addInstance(hello);
         hello.getVariable().setComment("Comments to the right");
         
         i = new VarBlock("i", null, 7, VarTypes.INT);
         i.setComment("Comments to the left of me");
         
         name2 = new ObjectBlock("name", null, 8, 9, null);
         name2.setObjectType("String");
         
         hwArgs = new ObjectBlock("args", null, 10, 11, null);
         hwArgs.setObjectType("String[]");
         
         sayHelloRef = new RefBlock(12, sayHello);
         sayHelloRef.addParam(new RefBlock(13, name2));
         
         makeHW = new RefBlock(14, hello);
         makeHW.addParam(new RefBlock(15,hwConst));
         
         helloRef = new RefBlock(16, hello);
         helloRef.addParam(sayHelloRef);
         helloRef.setComment("Stuck in the middle with you");
         
         litName = new VarBlock("Erik", null, 19, VarTypes.OBJECT, true);
         litName.setObjectType("String");
         litName.setValue("\"Erik\"", 0);
         
         litName2 = new VarBlock("Pasternak", null, 23, VarTypes.OBJECT, true);
         litName2.setObjectType("String");
         litName2.setValue("\" Pasternak\"", 0);
                
         combineName = new ExpressionBlock(null, 24, ExpressTypes.ARITH, "+");
         combineName.setFirstIn(new RefBlock(25,litName));
         combineName.setSecondIn(new RefBlock(26,litName2));
         
         setName = new ExpressionBlock(null, 17, ExpressTypes.ASSIGN, "=");
         setName.setFirstIn(new RefBlock(18,name2.getVariable()));
         setName.setSecondIn(new RefBlock(20,combineName));
         
         exp1 = new VarBlock("exp", null, 28, VarTypes.BOOLEAN);
         
         while1 = new FlowBlock("while", null, 27);
         while1.setCode(whileStart, 0);
         while1.addParam(exp1, 0);
         
         exp2 = new VarBlock("five", null, 32, VarTypes.INT, true);
         exp2.setValue("5", 0);
         
         cLT5 = new ExpressionBlock(null, 29, ExpressTypes.LOGIC, "<");
         cLT5.setFirstIn(new RefBlock(31, i));
         cLT5.setSecondIn(new RefBlock(33, exp2));
         
         whileRef = new RefBlock(30, while1);
         whileRef.addParam(new RefBlock(34, cLT5));
         whileRef.setIndex(0);
         whileRef.setComment("Here I am");
         
         whileRef2 = new RefBlock(35, while1);
         whileRef2.setIndex(3);
         whileRef2.setName("");
         
         one = new VarBlock("one", null, 38, VarTypes.INT, true);
         one.setValue("1", 0);
         
         ipp = new ExpressionBlock(null, 36, ExpressTypes.ASSIGN, "+=");
         ipp.setFirstIn(new RefBlock(37,i));
         ipp.setSecondIn(new RefBlock(39, one));
         
         cb = new CommentBlock("c1", null, 41);
         cb.setComment("CommentBlock can has comment");
         
         main = new BlockBlock("main", helloWorld, 21);
         main.setAccess(pubStatAcc);
         main.addParam(hwArgs.getVariable());
         main.addVar(i);
         main.addVar(hello.getVariable());
         main.addVar(name2.getVariable());
         main.addCode(makeHW);
         main.addCode(new RefBlock(22,setName));
         main.addCode(whileRef);
         main.addCode(helloRef);
         main.addCode(new RefBlock(40,ipp));
         main.addCode(new RefBlock(43, cb));
         main.addCode(whileRef2);
         main.addCode(new RefBlock(42,cb));
         
         //main.addReturn((new RefBlock(56,boolRet)));
         
         helloWorld.addMethodBlock(main);
         
      } catch(Exception e)
      {
         System.err.println(e.getMessage());
      }
   }
   
   public void paintComponent(Graphics g)
   {
      double scale = 0.75;
      super.paintComponent(g);
      this.drawer.setStart(10,10);
      this.drawer.setScale(scale);
//      this.drawer.drawAllIcons(g, this);
//      this.drawer.setStart(100,100);
      this.drawer.setGraphics(g);
      this.drawer.setObserver(this);
      this.helloWorld.getMethodBlocks().get(1).accept(drawer);
      this.drawer.setStart((int)(50*scale), (int)(36*scale));
      ((BlockBlock)(this.helloWorld.getMethodBlocks().get(1))).getVar(1).accept(drawer);
      
      this.drawer.setStart(10, (int)(76*scale));
      (new RefBlock(1,((CodeBlock)(this.helloWorld.getMethodBlocks().get(0))))).accept(drawer);
      this.drawer.setStart((int)(10+22*scale), (int)(209*scale));
      ((BlockBlock)(this.helloWorld.getMethodBlocks().get(1))).getCode().get(3).accept(drawer);
      this.drawer.setStart(10, (int)((209+27)*scale));
      this.drawer.drawIndent(ShapeDefs.FLOW_MID, 31);
      this.drawer.setStart(10, (int)((209+58)*scale));
      this.drawer.drawIndent(ShapeDefs.FLOW_MID, 31);
      this.drawer.setStart(10, (int)((209+89)*scale));
      this.drawer.drawIndent(ShapeDefs.FLOW_MID, 31);
      this.drawer.setStart(10, (int)(171*scale));
      ((BlockBlock)(this.helloWorld.getMethodBlocks().get(1))).getCode().get(2).accept(drawer);
      this.drawer.setStart(10, (int)(303*scale));
      ((BlockBlock)(this.helloWorld.getMethodBlocks().get(1))).getCode().get(6).accept(drawer);
      this.drawer.setStart(10, (int)(331*scale));
      ((BlockBlock)(this.helloWorld.getMethodBlocks().get(1))).getCode().get(1).accept(drawer);
      this.drawer.setStart((int)(10 + 30*scale), (int)((331+3)*scale));
      RefBlock tempFB = ((BlockBlock)(this.helloWorld.getMethodBlocks().get(1))).getCode().get(1);
      ((ExpressionBlock)(tempFB.getBlock())).getFirstIn().accept(drawer);
      this.drawer.setStart((int)(10 + 290*scale), (int)((331+0)*scale));
      ((ExpressionBlock)(tempFB.getBlock())).getSecondIn().accept(drawer);
      //this.drawer.drawAnImage(testImg, g, this);
      //g.drawString("test", 100, 100);
   }
}
